Class.forName 造成的线程阻塞

今天在查看服务器时,发现机器上稳定的会有 3 ~ 4 个线程处于阻塞状态,感觉应该是有问题的,仔细排查了一下,最终发现和 Class.forName 有关。

现象

某一天突然收到了公司的系统提醒,说是我们的服务中,长时间都有好几个处于BLOCKED状态的线程。

因为我们的访问量还是不小的,因此写了一段代码模拟了一下,大致类似于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Test {

public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), new ThreadFactory() {
private final AtomicInteger count = new AtomicInteger(0);

@Override
public Thread newThread(Runnable r) {
return new Thread(r, "testThread-" + count.incrementAndGet());
}
});

int totalCount = 1000;
CountDownLatch latch = new CountDownLatch(totalCount);
for (int i = 0; i < totalCount; i++) {
int current = i;
executor.execute(() -> {
try {
for (int j = 0; j < totalCount; j++) {
BeanInfo destBean = Introspector.getBeanInfo(Test.class, java.lang.Object.class);
}
} catch (Exception e) {
System.out.println(current + "\tfail");
} finally {
latch.countDown();
}
});
}

latch.await();
System.out.println("finish");
executor.shutdown();
}

private String name;

private Integer age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}
}

当时登上了服务器,首先查询出我们的 java 进程号:

1
ps -ef | grep java

假设结果是26385,这时再借助jstack命令打印出各个线程的状态:

1
jstack 26385 > 26385.txt

然后分析了26385.txt,发现了原因:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
"testThread-7" #14 prio=5 os_prio=0 tid=0x00007f72a810e800 nid=0x6706 waiting for monitor entry [0x00007f729837c000]
java.lang.Thread.State: BLOCKED (on object monitor)
at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
- waiting to lock <0x00000000f7c773b8> (a java.lang.Object)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at com.sun.beans.finder.ClassFinder.findClass(ClassFinder.java:103)
at java.beans.Introspector.findCustomizerClass(Introspector.java:1301)
at java.beans.Introspector.getTargetBeanDescriptor(Introspector.java:1295)
at java.beans.Introspector.getBeanInfo(Introspector.java:425)
at java.beans.Introspector.getBeanInfo(Introspector.java:262)
at java.beans.Introspector.getBeanInfo(Introspector.java:224)
at Test.lambda$main$0(Test.java:35)
at Test$$Lambda$1/303563356.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)

上面很清晰地显示了,我们写的Introspector.getBeanInfo代码,最终会调用Class类中的forName0方法:

1
2
3
4
5
/** Called after security check for system loader access checks have been made. */
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller)
throws ClassNotFoundException;

从上面的stack中分析可以得知,这个方法内部应该是有锁的,因此会阻塞其他线程。

解决方法

既然它是有锁的,为了不让它在运行时每次都执行,最简单的方法就是在初始化时,就将需要处理的类全部处理好,这样在应用运行期间,完全不会再去反射。

源码

有些人可能会好奇,forName0中究竟是如何使用到了锁,这里就把源码展示给大家。

下文的调用链路是:

1
forName0 -> JVM_FindClassFromCaller -> find_class_from_class_loader -> resolve_or_fail -> resolve_or_null -> resolve_instance_class_or_null -> load_instance_class

forName0源码实现位于src/java.base/share/native/libjava/Class.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 动态装载类型入口
JNIEXPORT jclass JNICALL
Java_java_lang_Class_forName0(JNIEnv *env, jclass this, jstring classname,
jboolean initialize, jobject loader, jclass caller)
{
char *clname;
jclass cls = 0;
char buf[128];
jsize len;
jsize unicode_len;

if (classname == NULL) {
JNU_ThrowNullPointerException(env, 0);
return 0;
}

// 把类全限定名里的'.'翻译成'/'
if (VerifyFixClassname(clname) == JNI_TRUE) {
/* slashes present in clname, use name b4 translation for exception */
(*env)->GetStringUTFRegion(env, classname, 0, unicode_len, clname);
JNU_ThrowClassNotFoundException(env, clname);
goto done;
}

// 验证类全限定名名合法性(是否以'/'分隔)
if (!VerifyClassname(clname, JNI_TRUE)) { /* expects slashed name */
JNU_ThrowClassNotFoundException(env, clname);
goto done;
}

// 从指定的加载器查找该类
cls = JVM_FindClassFromCaller(env, clname, initialize, loader, caller);

done:
if (clname != buf) {
free(clname);
}
return cls;
}

FindClassFromCaller位于src/hotspot/share/prims/jvm.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 从指定的加载器查找该类
// Find a class with this name in this loader, using the caller's protection domain.
JVM_ENTRY(jclass, JVM_FindClassFromCaller(JNIEnv* env, const char* name,
jboolean init, jobject loader,
jclass caller))

// 把当前类加入符号表(一个哈希表实现)
TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);

// 获取加载器和调用类
oop loader_oop = JNIHandles::resolve(loader);
oop from_class = JNIHandles::resolve(caller);
oop protection_domain = NULL;

if (from_class != NULL && loader_oop != NULL) {
protection_domain = java_lang_Class::as_Klass(from_class)->protection_domain();
}

// 查找该类
jclass result = find_class_from_class_loader(env, h_name, init, h_loader,
h_prot, false, THREAD);

// 返回结果
return result;
JVM_END

find_class_from_class_loader位于/src/hotspot/share/prims/jvm.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Shared JNI/JVM entry points //////////////////////////////////////////////////////////////
// 从指定的classloader中查找类
jclass find_class_from_class_loader(JNIEnv* env, Symbol* name, jboolean init,
Handle loader, Handle protection_domain,
jboolean throwError, TRAPS) {

//==========================================
//
// 根据指定的类名和加载器返回一个Klass对象,必要情况下需要加载该类。
// 如果未找到该类则抛出NoClassDefFoundError或ClassNotFoundException
//
//=========================================
Klass* klass = SystemDictionary::resolve_or_fail(name, loader, protection_domain, throwError != 0, CHECK_NULL);

// Check if we should initialize the class
if (init && klass->is_instance_klass()) {
klass->initialize(CHECK_NULL);
}
return (jclass) JNIHandles::make_local(env, klass->java_mirror());
}

resolve_or_fail位于src/hotspot/share/classfile/systemDictionary.cpp

1
2
3
4
5
6
7
8
9
10
// Forwards to resolve_or_null

Klass* SystemDictionary::resolve_or_fail(Symbol* class_name, Handle class_loader, Handle protection_domain, bool throw_error, TRAPS) {
Klass* klass = resolve_or_null(class_name, class_loader, protection_domain, THREAD);
if (HAS_PENDING_EXCEPTION || klass == NULL) {
// can return a null klass
klass = handle_resolution_exception(class_name, throw_error, klass, THREAD);
}
return klass;
}

resolve_or_null也位于src/hotspot/share/classfile/systemDictionary.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Forwards to resolve_instance_class_or_null

Klass* SystemDictionary::resolve_or_null(Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS) {

if (FieldType::is_array(class_name)) {
return resolve_array_class_or_null(class_name, class_loader, protection_domain, THREAD);
} else if (FieldType::is_obj(class_name)) {
ResourceMark rm(THREAD);
// Ignore wrapping L and ;.
TempNewSymbol name = SymbolTable::new_symbol(class_name->as_C_string() + 1,
class_name->utf8_length() - 2, CHECK_NULL);
return resolve_instance_class_or_null(name, class_loader, protection_domain, THREAD);
} else {
// 解析实例类
return resolve_instance_class_or_null(class_name, class_loader, protection_domain, THREAD);
}
}

resolve_instance_class_or_null也位于src/hotspot/share/classfile/systemDictionary.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
Klass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
Handle class_loader,
Handle protection_domain,
TRAPS) {
Handle lockObject = compute_loader_lock_object(class_loader, THREAD);
check_loader_lock_contention(lockObject, THREAD);
// 获取对象锁
ObjectLocker ol(lockObject, THREAD, DoObjectLock);

{
MutexLocker mu(SystemDictionary_lock, THREAD);
// 查找类
InstanceKlass* check = find_class(d_index, d_hash, name, dictionary);
if (check != NULL) {
// Klass is already loaded, so just return it
class_has_been_loaded = true;
k = check;
} else {
// 查找该类是否在placeholder table中
placeholder = placeholders()->get_entry(p_index, p_hash, name, loader_data);
if (placeholder && placeholder->super_load_in_progress()) {
super_load_in_progress = true;
if (placeholder->havesupername() == true) {
superclassname = placeholder->supername();
havesupername = true;
}
}
}
}

// 如果该类在placeholder table中,则说明类加载进行中
if (super_load_in_progress && havesupername==true) {
k = handle_parallel_super_load(name,
superclassname,
class_loader,
protection_domain,
lockObject, THREAD);
if (HAS_PENDING_EXCEPTION) {
return NULL;
}
if (k != NULL) {
class_has_been_loaded = true;
}
}

bool throw_circularity_error = false;
if (!class_has_been_loaded) {
bool load_instance_added = false;

if (!class_has_been_loaded) {

// =====================================
//
// 执行实例加载动作
//
// =====================================
k = load_instance_class(name, class_loader, THREAD);

if (!HAS_PENDING_EXCEPTION && k != NULL &&
k->class_loader() != class_loader()) {

check_constraints(d_index, d_hash, k, class_loader, false, THREAD);

// Need to check for a PENDING_EXCEPTION again; check_constraints
// can throw and doesn't use the CHECK macro.
if (!HAS_PENDING_EXCEPTION) {
{ // Grabbing the Compile_lock prevents systemDictionary updates
// during compilations.
MutexLocker mu(Compile_lock, THREAD);
update_dictionary(d_index, d_hash, p_index, p_hash,
k, class_loader, THREAD);
}

// 通知JVMTI类加载事件
if (JvmtiExport::should_post_class_load()) {
Thread *thread = THREAD;
assert(thread->is_Java_thread(), "thread->is_Java_thread()");
JvmtiExport::post_class_load((JavaThread *) thread, k);
}
}
}
} // load_instance_class
}
...

return k;
}

load_instance_class也位于src/hotspot/share/classfile/systemDictionary.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// ===================================================================================
//
// 加载实例class,这里有两种方式:
// ===================================================================================
//
// 1、如果classloader为null则说明是加载系统类,使用bootstrap loader
// 调用方式:直接调用ClassLoader::load_class()加载该类
//
// 2、如果classloader不为null则说明是非系统类,使用ext/app/自定义 classloader
// 调用方式:通过JavaCalls::call_virtual()调用Java方法ClassLoader.loadClass()加载该类
//
// ===================================================================================
InstanceKlass* SystemDictionary::load_instance_class(Symbol* class_name, Handle class_loader, TRAPS) {

// 使用bootstrap加载器加载
if (class_loader.is_null()) {

// 根据全限定名获取包名
// Find the package in the boot loader's package entry table.
TempNewSymbol pkg_name = InstanceKlass::package_from_name(class_name, CHECK_NULL);
if (pkg_name != NULL) {
pkg_entry = loader_data->packages()->lookup_only(pkg_name);
}

InstanceKlass* k = NULL;

if (k == NULL) {
// Use VM class loader
PerfTraceTime vmtimer(ClassLoader::perf_sys_classload_time());
// =================================================================
//
// 使用bootstrap loader加载该类
//
// =================================================================
k = ClassLoader::load_class(class_name, search_only_bootloader_append, CHECK_NULL);
}


return k;
} else {
// =======================================================================================
//
// 使用用户指定的加载器加载该类,调用class_loader的loadClass操作方法,
// 最终返回一个标准的InstanceKlass,流程如下
//
// +-----------+ loadClass() +---------------+ get_jobject() +-------------+
// | className | -------------> | JavaValue | ---------------> | oop |
// +-----------+ +---------------+ +-------------+
// |
// | as_Klass()
// v
// +---------------+ cast() +-------------+
// | InstanceKlass | <--------------- | Klass |
// +---------------+ +-------------+
//
// =======================================================================================
ResourceMark rm(THREAD);

assert(THREAD->is_Java_thread(), "must be a JavaThread");
JavaThread* jt = (JavaThread*) THREAD;

PerfClassTraceTime vmtimer(ClassLoader::perf_app_classload_time(),
ClassLoader::perf_app_classload_selftime(),
ClassLoader::perf_app_classload_count(),
jt->get_thread_stat()->perf_recursion_counts_addr(),
jt->get_thread_stat()->perf_timers_addr(),
PerfClassTraceTime::CLASS_LOAD);

Handle s = java_lang_String::create_from_symbol(class_name, CHECK_NULL);
// Translate to external class name format, i.e., convert '/' chars to '.'
Handle string = java_lang_String::externalize_classname(s, CHECK_NULL);

JavaValue result(T_OBJECT);

InstanceKlass* spec_klass = SystemDictionary::ClassLoader_klass();

// Added MustCallLoadClassInternal in case we discover in the field
// a customer that counts on this call
if (MustCallLoadClassInternal && has_loadClassInternal()) {
JavaCalls::call_special(&result,
class_loader,
spec_klass,
vmSymbols::loadClassInternal_name(),
vmSymbols::string_class_signature(),
string,
CHECK_NULL);
} else {
// ===============================================================
//
// 调用ClassLoader.loadClass()方法加载该类,而最终会调用ClassLoader的native方法defineClass1()
// 其实现位于ClassLoader.c # Java_java_lang_ClassLoader_defineClass1()
//
// ===============================================================
JavaCalls::call_virtual(&result,
class_loader,
spec_klass,
vmSymbols::loadClass_name(),
vmSymbols::string_class_signature(),
string,
CHECK_NULL);
}

assert(result.get_type() == T_OBJECT, "just checking");
// 获取oop对象
oop obj = (oop) result.get_jobject();

// 如果不是基本类,则转换成对应的InstanceKlass
if ((obj != NULL) && !(java_lang_Class::is_primitive(obj))) {
InstanceKlass* k = InstanceKlass::cast(java_lang_Class::as_Klass(obj));

if (class_name == k->name()) {
// 返回最终InstanceKlass
return k;
}
}
// Class is not found or has the wrong name, return NULL
return NULL;
}
}

以上便是相关的所有底层源码。

总结

一个小小的Class.forName方法,也会引出不少问题,如果仔细研究,在排查的过程,相信你一定会有所收获。

有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

https://death00.github.io/

公众号:健程之道



健健 wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
如果您感觉文章不错,也愿意支持一下作者的话